home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 2000 August: Tool Chest / Dev.CD Aug 00 TC Disk 2.toast / pc / sample code / quicktime / all macintosh / quicktime vr / vrmakepano / vrmakepano.c < prev    next >
Encoding:
Text File  |  2000-06-23  |  58.4 KB  |  1,704 lines

  1. //////////
  2. //
  3. //    File:        VRMakePano.c
  4. //
  5. //    Contains:    Code for creating a QuickTime VR panoramic movie from a panoramic image.
  6. //
  7. //    Written by:    Tim Monroe
  8. //                Based largely on MakeQTVRPanorama code by Ed Harp (and others?).
  9. //
  10. //    Copyright:    © 1996-1998 by Apple Computer, Inc., all rights reserved.
  11. //
  12. //    Change History (most recent first):
  13. //
  14. //       <11>         02/01/99    rtm        reworked prompt and filename handling to remove "\p" sequences
  15. //       <10>         10/19/98    rtm        added check in VRPano_CreatePanoTrack to make sure we were passed a
  16. //                                    valid hot spot file spec (otherwise no panorama is created!); added
  17. //                                    the default movie progress procedure
  18. //       <9>         09/30/98    rtm        tweaked call to AddMovieResource to create single-fork movies;
  19. //                                    tweaked call to FlattenMovieData to enable FastStart option
  20. //       <8>         06/08/98    rtm        added VRPano_MakeHotSpotVers2x0 (based loosely on code from John Mott)
  21. //                                    to support version 2.0 hot spot atoms; sketched out preliminary
  22. //                                    support for 1.0 hot spots, but that's left as an exercise for the reader
  23. //       <7>         05/08/98    rtm        added support for hot spots image tracks (both version 1.0 and 2.0)
  24. //       <6>         26/01/98    rtm        added support for pan/tilt/field-of-view constraint atoms
  25. //       <5>         18/01/98    rtm        added support for version 1.0 panoramic movies
  26. //       <4>         16/01/98    rtm        final clean-up before initial release
  27. //       <3>         15/01/98    rtm        reworked the image file handling using QuickTime graphics importers;
  28. //                                    added tile-display window for debugging purposes;
  29. //                                    now we run fine under both MacOS and Windows. Ta da!
  30. //       <2>         14/01/98    rtm        further work; added Endian* macros; got working on MacOS
  31. //       <1>         12/01/98    rtm        first file from CMovieMaker.cp in MakeQTVRPanorama 2.0b
  32. //
  33. //    This file contains functions that convert a panoramic image into a QuickTime VR movie. The image
  34. //    can be a picture (of type 'PICT') or any other kind of image for which QuickTime has a graphics
  35. //    importer component. Here, we can create both version 1.0 and version 2.0 QTVR panoramic movies.
  36. //    We can also create hot spot image tracks and (for version 2.0 only) assemble the various hot spot
  37. //    atoms.
  38. //
  39. //
  40. //    VERSION 2.0 FILE FORMAT
  41. //
  42. //    The definitive source of information about creating QTVR 2.0 panoramic movies is Chapter 3 of the
  43. //    book "Virtual Reality Programming With QuickTime VR 2.0". (This information is also available
  44. //    online, at <http://dev.info.apple.com/dev/techsupport/insidemac/qtvr/qtvrapi-2.html>.) Here is
  45. //    a condensed version of the info in that chapter, as pertains to panoramas:
  46. //
  47. //    A panoramic movie is a QuickTime movie that contains at least three tracks: a QTVR track, a panorama
  48. //    track, and a panorama image track. In addition, a QuickTime VR movie must contain some special user data
  49. //    that specifies the QuickTime VR movie controller. A QuickTime VR movie can also contain other kinds of
  50. //    tracks, such as hot spot image tracks and even sound tracks.
  51. //
  52. //    A QuickTime VR movie contains a single "QTVR track", which maintains a list of the nodes in the movie.
  53. //    Each individual sample in the QTVR track's media contains information about a single node, such as the
  54. //    node's type, ID, and name. Since we are creating a single-node movie here, our QTVR track will contain
  55. //    a single media sample. 
  56. //
  57. //    Every media sample in a QTVR track has the same sample description, whose type is QTVRSampleDescription.
  58. //    The data field of that sample description is a "VR world", an atom container whose child atoms specify
  59. //    information about the nodes in the movie, such as the default node ID and the default imaging properties.
  60. //    We'll spend a good bit of time putting things into the VR world.
  61. //
  62. //    A panoramic movie also contains a single "panorama track", which contains information specific to the
  63. //    panorama. A panorama track has a media sample for each media sample in the QTVR track. As a result,
  64. //    our panorama track will have one sample. The QTVRPanoSampleAtom structure defines the media sample data. 
  65. //
  66. //    The actual image data for a panoramic node is contained in a "panorama image track". The individual
  67. //    frames in that track are the diced (and also perhaps compressed) tiles of the original panoramic image.
  68. //    There may also be a "hot spot image track" that contains the diced (and also perhaps compressed) tiles
  69. //    of the hot spot panoramic image.
  70. //
  71. //    So, our general strategy, given a panoramic image, is as follows (though perhaps not in the order listed):
  72. //
  73. //        (1) Create a movie containing a video track whose frames are the compressed tiles of the panoramic
  74. //            image. Call this movie the "tile movie". Create a similar movie for the hot spot image. Call
  75. //            this movie the "hot spot tile movie".
  76. //        (2) Create a new, empty movie. Call this movie the "QTVR movie".
  77. //        (3) Create a QTVR track and its associated media.
  78. //        (4) Create a VR world atom container; this is stored in the sample description for the QTVR track.
  79. //        (5) Create a node information atom container for each node; this is stored as a media sample
  80. //            in the QTVR track.
  81. //        (6) Create a panorama track and add it to the movie.
  82. //        (7)    Create a panorama image track by copying the video track from the tile movie to the QTVR movie.
  83. //        (8)    Create a hot spot image track by copying the video track from the hot spot tile movie to the QTVR movie.
  84. //        (9) Set up track references from the QTVR track to the panorama track, and from the panorama track
  85. //            to the panorama image track and the hot spot image track.
  86. //        (A) Add a user data item that identifies the QTVR movie controller.
  87. //        (B) Flatten the QTVR movie into the final panoramic movie.
  88. //
  89. //
  90. //    VERSION 1.0 FILE FORMAT
  91. //
  92. //    The definitive source of information about creating QTVR 1.0 panoramic movies is Technote 1035,
  93. //    "QuickTime VR 1.0 Panorama Movie File Format" released in March 1996, available online at the address
  94. //    <http://devworld.apple.com/dev/technotes/tn/tn1035.html>. Here is a condensed version of the info
  95. //    in that technote:
  96. //
  97. //    For version 1.0 panoramic movies, the file format is somewhat simpler. A single-node panoramic movie
  98. //    contains a "scene track" and a "panorama track". The scene track is an inactive video track that contains
  99. //    the diced (and also perhaps compressed) tiles of the original panoramic image. A version 1.0 scene track
  100. //    is essentially a version 2.0 panorama image track.
  101. //
  102. //    A panoramic movie also contains a single "panorama track", which contains information specific to the
  103. //    panorama. The panorama track in version 1.0 differs significantly from the panorama track in version 2.0,
  104. //    so don't let the names confuse you. 
  105. //
  106. //    A "panorama track" in version 1.0 contains one media sample for each node in the movie. Every media sample
  107. //    in a panorama track has the same sample description, whose type is PanoramaDescription. This structure
  108. //    contains information about how the panoramic image was diced, along with information about any hot spot
  109. //    and low-resolution scene tracks that might be contained in the movie file.
  110. //
  111. //    Each individual sample in the panorama track's media contains information about a single node,
  112. //    such as the node's default view angles, pan and zoom constraints, and hot spot information.
  113. //    Since we are creating a single-node movie here, our panorama track will contain a single media sample. 
  114. //    The data in a panorama track sample is organized as a sequence of "atom data structures"; each such
  115. //    structure begins with size and type fields, so you can easily read thru the atom data structures by
  116. //    starting at the beginning of the sample data and hopping over each structure. The atoms can appear in
  117. //    any order in the panorama track sample data.
  118. //
  119. //    -> IMPORTANT: The "atom data structures" that comprise a panorama track sample are *not* QTAtoms, and the
  120. //    -> panorama track sample itself is *not* a QTAtomContainer. The version 1.0 panorama file format predates
  121. //    -> QuickTime 2.1, which introduced the atom data routines. As a result, you cannot use the atom data routines
  122. //    -> to read or write version 1.0 panorama track samples.
  123. //
  124. //    A panorama track sample must contain a "panorama header atom", whose structure is PanoSampleHeaderAtom.
  125. //    The sample may contain other atoms as well, such as a string table atom (which contains strings, such as
  126. //    node names) and a hot spot table atom (which lists all of the hot spots in a node). In this sample code,
  127. //    we add a hot spot image track to the VR movie, but we do not (yet) build a hot spot atom table; as a result,
  128. //    our panorama track sample will need to contain only a panorama header atom.
  129. //
  130. //    So, our general strategy, given a panoramic image, is as follows (though perhaps not in the order listed):
  131. //
  132. //        (1) Create a movie containing a video track whose frames are the compressed tiles of the panoramic
  133. //            image. Call this movie the "tile movie". Create a similar movie for the hot spot image. Call
  134. //            this movie the "hot spot tile movie".
  135. //        (2) Create a new, empty movie. Call this movie the "QTVR movie".
  136. //        (3)    Create a scene track by copying the video track from the tile movie to the QTVR movie.
  137. //        (4)    Create a hot spot image track by copying the video track from the hot spot tile movie to the QTVR movie.
  138. //        (5) Create a panorama track and add it to the movie.
  139. //        (6)    Add a user data item that identifies the QTVR movie controller.
  140. //        (7) Flatten the QTVR movie into the final panoramic movie.
  141. //
  142. //
  143. //    NOTES:
  144. //
  145. //    *** (1) ***
  146. //    This code is based largely on the existing MakeQTVRPanorama sample code written by Ed Harp (and others?).
  147. //    MakeQTVRPanorama is a full-featured application written in C++ using Metrowerks' PowerPlant, a Mac-only
  148. //    application framework. Here I've uncoupled the central portion of that code, contained in the file CMovieMaker.cp,
  149. //    and converted it into straight C. I have taken the liberty of reworking that code as necessary to make it run
  150. //    also on Windows platforms and to bring it into line with the other QuickTime code samples. So far the biggest
  151. //    changes involve using graphics importers instead of the original PICT-reading code and inserting all those
  152. //    Endian macros.
  153. //
  154. //    *** (2) ***
  155. //    All data in QTAtom structures must be in big-endian format. We use macros like EndianU32_NtoB to convert
  156. //    values into the proper format before inserting them into atoms. See VRPano_CreateVRWorld for some examples.
  157. //
  158. //    *** (3) ***
  159. //    This sample code prompts the user for a panoramic image file (which is required) and a hot spot image file
  160. //    (which is optional). If the user hits the Cancel button while being prompted for a hot spot image file, no
  161. //    hot spot image track will be added to the resulting VR movie file. If the user does select a hot spot image
  162. //    file, this sample code builds the hot spot info atoms (version 2.0) or the hot spot table atom (version 1.0)
  163. //    required to attach names and other information to the individual hot spots. 
  164. //
  165. //////////
  166.  
  167. #include "VRMakePano.h"
  168.  
  169. UInt32                    gVersionToCreate = kQTVRVersion2;        // the version of the file format we create; default to version 2.0
  170.  
  171.  
  172. //////////
  173. //
  174. // VRPano_PromptUserForImageFileAndMakeMovie
  175. // Let the user select a panoramic image file, then make a panoramic movie from it.
  176. //
  177. //////////
  178.  
  179. void VRPano_PromptUserForImageFileAndMakeMovie (void)
  180. {
  181.     SFTypeList                myTypeList;
  182.     StandardFileReply        myReply;
  183.     FSSpec                    myPictSpec;
  184.     FSSpec                    myHSPictSpec;
  185.     FSSpec                    myTileSpec;
  186.     FSSpec                    myDestSpec;
  187.     StringPtr                 myTilePrompt = QTUtils_ConvertCToPascalString(kPanoSaveTilePrompt);
  188.     StringPtr                 myTileFileName = QTUtils_ConvertCToPascalString(kPanoSaveTileFileName);
  189.     StringPtr                 myMoviePrompt = QTUtils_ConvertCToPascalString(kPanoSaveMoviePrompt);
  190.     StringPtr                 myMovieFileName = QTUtils_ConvertCToPascalString(kPanoSaveMovieFileName);
  191.  
  192.     // have the user select an image file;
  193.     // kQTFileTypeQuickTimeImage means any image file readable by GetGraphicsImporterForFile
  194.     myTypeList[0] = kQTFileTypeQuickTimeImage;
  195.  
  196.     StandardGetFilePreview(NULL, 1, myTypeList, &myReply);
  197.     if (!myReply.sfGood)
  198.         return;
  199.     
  200.     myPictSpec = myReply.sfFile;
  201.  
  202.     // have the user select a hot spot image file;
  203.     // kQTFileTypeQuickTimeImage means any image file readable by GetGraphicsImporterForFile
  204.     myTypeList[0] = kQTFileTypeQuickTimeImage;
  205.  
  206.     StandardGetFilePreview(NULL, 1, myTypeList, &myReply);
  207.     if (!myReply.sfGood) {
  208.         // we allow the user to cancel, to indicate no hot spot image
  209.         myReply.sfFile.vRefNum = 0;
  210.         myReply.sfFile.parID = 0;
  211.         myReply.sfFile.name[0] = 0;        // set length to 0
  212.     }
  213.         
  214.     myHSPictSpec = myReply.sfFile;
  215.  
  216.     // have the user select the name of the new tile movie file
  217.     StandardPutFile(myTilePrompt, myTileFileName, &myReply);
  218.     if (!myReply.sfGood)
  219.         return;
  220.  
  221.     myTileSpec = myReply.sfFile;
  222.     
  223.     // have the user select the name of the new panoramic movie file
  224.     StandardPutFile(myMoviePrompt, myMovieFileName, &myReply);
  225.     if (!myReply.sfGood)
  226.         return;
  227.  
  228.     myDestSpec = myReply.sfFile;
  229.  
  230.     // just do it...
  231.     VRPano_MakePanorama(&myPictSpec, &myHSPictSpec, &myTileSpec, &myDestSpec, kPanoramaWidth, kPanoramaHeight, kCinepakCodecType, codecHighQuality, gVersionToCreate);
  232.  
  233.     // ...and let the user know we're done
  234.     DoBeep();
  235.     
  236.     // now clean up after ourselves
  237.     DeleteMovieFile(&myTileSpec);
  238.     
  239.     free(myTilePrompt);
  240.     free(myTileFileName);
  241.     free(myMoviePrompt);
  242.     free(myMovieFileName);
  243. }
  244.  
  245.  
  246. //////////
  247. //
  248. // VRPano_CreateVRWorld
  249. // Create a VR world atom container and add the basic required atoms to it. Also, create a
  250. // node information atom container and add a node header atom to it. Return both atom containers.
  251. //
  252. // The caller is responsible for disposing of the VR world and the node information atom
  253. // (by calling QTDisposeAtomContainer).
  254. //
  255. // This function assumes that the scene described by the VR world contains a single node whose
  256. // type is specified by the theNodeType parameter.
  257. //
  258. //////////
  259.  
  260. OSErr VRPano_CreateVRWorld (QTAtomContainer *theVRWorld, QTAtomContainer *theNodeInfo, OSType theNodeType)
  261. {
  262.     QTAtomContainer            myVRWorld = NULL;
  263.     QTAtomContainer            myNodeInfo = NULL;
  264.     QTVRWorldHeaderAtom        myVRWorldHeaderAtom;
  265.     QTAtom                    myImagingParentAtom;
  266.     QTAtom                    myNodeParentAtom;
  267.     QTAtom                    myHSParentAtom;
  268.     QTAtom                    myNodeAtom;
  269.     QTVRPanoImagingAtom        myPanoImagingAtom;
  270.     QTVRNodeLocationAtom    myNodeLocationAtom;
  271.     QTVRNodeHeaderAtom        myNodeHeaderAtom;
  272.     UInt32                    myIndex;
  273.     OSErr                    myErr = noErr;
  274.  
  275.     //////////
  276.     //
  277.     // create a VR world atom container
  278.     //
  279.     //////////
  280.  
  281.     myErr = QTNewAtomContainer(&myVRWorld);
  282.     if (myErr != noErr)
  283.         goto bail;
  284.  
  285.     //////////
  286.     //
  287.     // add a VR world header atom to the VR world
  288.     //
  289.     //////////
  290.  
  291.     myVRWorldHeaderAtom.majorVersion = EndianU16_NtoB(kQTVRMajorVersion);
  292.     myVRWorldHeaderAtom.minorVersion = EndianU16_NtoB(kQTVRMinorVersion);
  293.  
  294.     // insert the scene name string, if we have one; if not, set nameAtomID to 0
  295.     if (false) {
  296.         Str255                myStr = "\pMy Scene";
  297.         QTAtomID            myID;
  298.         
  299.         myErr = VRPano_AddStr255ToAtomContainer(myVRWorld, kParentAtomIsContainer, myStr, &myID);
  300.         myVRWorldHeaderAtom.nameAtomID = EndianU32_NtoB(myID);
  301.     } else
  302.         myVRWorldHeaderAtom.nameAtomID = EndianU32_NtoB(0L);
  303.     
  304.     myVRWorldHeaderAtom.defaultNodeID = EndianU32_NtoB(kDefaultNodeID);
  305.     myVRWorldHeaderAtom.vrWorldFlags = EndianU32_NtoB(0L);
  306.     myVRWorldHeaderAtom.reserved1 = EndianU32_NtoB(0L);
  307.     myVRWorldHeaderAtom.reserved2 = EndianU32_NtoB(0L);
  308.  
  309.     // add the atom to the atom container (the VR world)
  310.     myErr = QTInsertChild(myVRWorld, kParentAtomIsContainer, kQTVRWorldHeaderAtomType, 1, 1, sizeof(QTVRWorldHeaderAtom), &myVRWorldHeaderAtom, NULL);
  311.     if (myErr != noErr)
  312.         goto bail;
  313.         
  314.     //////////
  315.     //
  316.     // add an imaging parent atom to the VR world and insert imaging atoms into it
  317.     //
  318.     // imaging atoms describe the default imaging characteristics for the different types of nodes in the scene;
  319.     // currently, only the panorama imaging atoms are defined, so we'll include those (even in object movies)
  320.     //
  321.     //////////
  322.     
  323.     myErr = QTInsertChild(myVRWorld, kParentAtomIsContainer, kQTVRImagingParentAtomType, 1, 1, 0, NULL, &myImagingParentAtom);
  324.     if (myErr != noErr)
  325.         goto bail;
  326.         
  327.     // fill in the fields of the panorama imaging atom structure
  328.     myPanoImagingAtom.majorVersion = EndianU16_NtoB(kQTVRMajorVersion);
  329.     myPanoImagingAtom.minorVersion = EndianU16_NtoB(kQTVRMinorVersion);
  330.     myPanoImagingAtom.correction = EndianU32_NtoB(kQTVRFullCorrection);
  331.     myPanoImagingAtom.imagingValidFlags = EndianU32_NtoB(kQTVRValidCorrection | kQTVRValidQuality | kQTVRValidDirectDraw);
  332.     for (myIndex = 0; myIndex < 6; myIndex++)
  333.         myPanoImagingAtom.imagingProperties[myIndex] = EndianU32_NtoB(0L);
  334.     myPanoImagingAtom.reserved1 = EndianU32_NtoB(0L);
  335.     myPanoImagingAtom.reserved2 = EndianU32_NtoB(0L);
  336.     
  337.     // add a panorama imaging atom for kQTVRMotion state
  338.     myPanoImagingAtom.quality = EndianU32_NtoB(codecLowQuality);
  339.     myPanoImagingAtom.directDraw = EndianU32_NtoB(true);
  340.     myPanoImagingAtom.imagingMode = EndianU32_NtoB(kQTVRMotion);
  341.     myErr = QTInsertChild(myVRWorld, myImagingParentAtom, kQTVRPanoImagingAtomType, 0, 0, sizeof(QTVRPanoImagingAtom), &myPanoImagingAtom, NULL);
  342.     if (myErr != noErr)
  343.         goto bail;
  344.         
  345.     // add a panorama imaging atom for kQTVRStatic state
  346.     myPanoImagingAtom.quality = EndianU32_NtoB(codecHighQuality);
  347.     myPanoImagingAtom.directDraw = EndianU32_NtoB(false);
  348.     myPanoImagingAtom.imagingMode = EndianU32_NtoB(kQTVRStatic);
  349.     myErr = QTInsertChild(myVRWorld, myImagingParentAtom, kQTVRPanoImagingAtomType, 0, 0, sizeof(QTVRPanoImagingAtom), &myPanoImagingAtom, NULL);
  350.     if (myErr != noErr)
  351.         goto bail;
  352.         
  353.     //////////
  354.     //
  355.     // add a node parent atom to the VR world and insert node ID atoms into it
  356.     //
  357.     //////////
  358.     
  359.     myErr = QTInsertChild(myVRWorld, kParentAtomIsContainer, kQTVRNodeParentAtomType, 1, 1, 0, NULL, &myNodeParentAtom);
  360.     if (myErr != noErr)
  361.         goto bail;
  362.         
  363.     // add a node ID atom
  364.     myErr = QTInsertChild(myVRWorld, myNodeParentAtom, kQTVRNodeIDAtomType, kDefaultNodeID, 0, 0, NULL, &myNodeAtom);
  365.     if (myErr != noErr)
  366.         goto bail;
  367.     
  368.     // add a single node location atom to the node ID atom
  369.     myNodeLocationAtom.majorVersion = EndianU16_NtoB(kQTVRMajorVersion);
  370.     myNodeLocationAtom.minorVersion = EndianU16_NtoB(kQTVRMinorVersion);
  371.     myNodeLocationAtom.nodeType = EndianU32_NtoB(theNodeType);
  372.     myNodeLocationAtom.locationFlags = EndianU32_NtoB(kQTVRSameFile);
  373.     myNodeLocationAtom.locationData = EndianU32_NtoB(0);
  374.     myNodeLocationAtom.reserved1 = EndianU32_NtoB(0);
  375.     myNodeLocationAtom.reserved2 = EndianU32_NtoB(0);
  376.     
  377.     // insert the node location atom into the node ID atom
  378.     myErr = QTInsertChild(myVRWorld, myNodeAtom, kQTVRNodeLocationAtomType, 1, 1, sizeof(QTVRNodeLocationAtom), &myNodeLocationAtom, NULL);
  379.     if (myErr != noErr)
  380.         goto bail;
  381.     
  382.     //////////
  383.     //
  384.     // create a node information atom container and add a node header atom to it
  385.     //
  386.     //////////
  387.     
  388.     myErr = QTNewAtomContainer(&myNodeInfo);
  389.     if (myErr != noErr)
  390.         goto bail;
  391.  
  392.     myNodeHeaderAtom.majorVersion = EndianU16_NtoB(kQTVRMajorVersion);
  393.     myNodeHeaderAtom.minorVersion = EndianU16_NtoB(kQTVRMinorVersion);
  394.     myNodeHeaderAtom.nodeType = EndianU32_NtoB(theNodeType);
  395.     myNodeHeaderAtom.nodeID = EndianU32_NtoB(kDefaultNodeID);
  396.     myNodeHeaderAtom.commentAtomID = EndianU32_NtoB(0L);
  397.     myNodeHeaderAtom.reserved1 = EndianU32_NtoB(0L);
  398.     myNodeHeaderAtom.reserved2 = EndianU32_NtoB(0L);
  399.     
  400.     // insert the node name string into the node info atom container
  401.     if (false) {
  402.         Str255                myStr = "\pMy Node";
  403.         QTAtomID            myID;
  404.         
  405.         myErr = VRPano_AddStr255ToAtomContainer(myNodeInfo, kParentAtomIsContainer, myStr, &myID);
  406.         myNodeHeaderAtom.nameAtomID = EndianU32_NtoB(myID);
  407.     } else
  408.         myNodeHeaderAtom.nameAtomID = EndianU32_NtoB(0L);
  409.     
  410.     // insert the node header atom into the node info atom container
  411.     myErr = QTInsertChild(myNodeInfo, kParentAtomIsContainer, kQTVRNodeHeaderAtomType, 1, 1, sizeof(QTVRNodeHeaderAtom), &myNodeHeaderAtom, NULL);
  412.     if (myErr != noErr)
  413.         goto bail;
  414.     
  415.     //////////
  416.     //
  417.     // add a hot spot parent atom to the node information atom container and insert hot spot atoms into it
  418.     //
  419.     //////////
  420.         
  421.     // insert the hot spot parent atom into the node info atom container
  422.     myErr = QTInsertChild(myNodeInfo, kParentAtomIsContainer, kQTVRHotSpotParentAtomType, 1, 1, 0, NULL, &myHSParentAtom);
  423.     if (myErr != noErr)
  424.         goto bail;
  425.         
  426.     // the following loop adds all possible hot spot atoms to the hot spot parent atom;
  427.     // we do this because we don't know how many hot spots are in the hot spot image or
  428.     // what their color-table indices are; you will want to handle a real hot spot image
  429.     // differently, no doubt....
  430.     
  431.     for (myIndex = 1; myIndex < 255; myIndex++) {
  432.         char        myHSName[100];
  433.         char        myURL[] = "http://www.apple.com";
  434.         
  435.         sprintf(myHSName, "Hot Spot Index %d", myIndex);
  436.  
  437.         myErr = VRPano_MakeHotSpotVers2x0(myNodeInfo, myHSParentAtom, myURL, myHSName, myIndex);
  438.         if (myErr != noErr)
  439.             goto bail;
  440.     }
  441.     
  442. bail:
  443.     // return the atom containers that we've created and configured here
  444.     *theVRWorld = myVRWorld;
  445.     *theNodeInfo = myNodeInfo;
  446.     
  447.     return(myErr);
  448. }
  449.  
  450.  
  451. //////////
  452. //
  453. // VRPano_CreatePanoTrack
  454. // Create a (version 2.0) panorama track.
  455. //
  456. //////////
  457.  
  458. OSErr VRPano_CreatePanoTrack (Movie theMovie, FSSpec *theSrcFile, FSSpec *theHSSrcFile, Track theQTVRTrack, Track thePanoTrack, Media thePanoMedia, long theWidth, long theHeight)
  459. {
  460.     Track                        myImageTrack = NULL;
  461.     Track                        myHSImageTrack = NULL;
  462.     TimeValue                    myDuration = 7200;
  463.     Movie                        mySrcMovie = NULL;
  464.     Movie                        myHSSrcMovie = NULL;
  465.     long                        myRefIndex = 0;
  466.     long                        myHSRefIndex = 0;
  467.     short                        mySrcRefNum = 0;
  468.     short                        myHSSrcRefNum = 0;
  469.     short                        myResID = 0;
  470.     short                        myHSResID = 0;
  471.     SampleDescriptionHandle        mySampleDesc = NULL;
  472.     QTAtomContainer                myPanoSample;
  473.     QTVRPanoSampleAtom            myPanoSampleData;
  474.     int                            myNumFramesX = 1;
  475.     int                            myNumFramesY = kNumTilesInPanoFile;
  476.     double                        myTheta;
  477.     OSErr                        myErr = noErr;
  478.  
  479.     //////////
  480.     //
  481.     // create a panorama image track; a reference to it is contained in the pano track
  482.     //
  483.     //////////
  484.     
  485.     // open the source file
  486.     myErr = OpenMovieFile(theSrcFile, &mySrcRefNum, fsRdPerm);
  487.     if (myErr != noErr)
  488.         goto bail;
  489.     
  490.     myErr = NewMovieFromFile(&mySrcMovie, mySrcRefNum, &myResID, 0, 0, 0);
  491.     if (myErr != noErr)
  492.         goto bail;
  493.     
  494.     myErr = VRPano_ImportVideoTrack(mySrcMovie, theMovie, myDuration, &theWidth, &theHeight, &myImageTrack);
  495.     if (myErr != noErr)
  496.         goto bail;
  497.     
  498.     //////////
  499.     //
  500.     // create a hot spot image track, if the user has previously selected a hot spot image;
  501.     // a reference to the hot spot image track is contained in the pano track
  502.     //
  503.     //////////
  504.     
  505.     if (theHSSrcFile->name[0] != 0) {
  506.     
  507.         // open the source file
  508.         myErr = OpenMovieFile(theHSSrcFile, &myHSSrcRefNum, fsRdPerm);
  509.         if (myErr != noErr)
  510.             goto bail;
  511.         
  512.         myErr = NewMovieFromFile(&myHSSrcMovie, myHSSrcRefNum, &myHSResID, 0, 0, 0);
  513.         if (myErr != noErr)
  514.             goto bail;
  515.         
  516.         myErr = VRPano_ImportVideoTrack(myHSSrcMovie, theMovie, myDuration, &theWidth, &theHeight, &myHSImageTrack);
  517.         if (myErr != noErr)
  518.             goto bail;
  519.     }    
  520.     
  521.     //////////
  522.     //
  523.     // create track references from QTVR track to panorama track,
  524.     // and from the panorama track to the panorama image track and the hot spot image track
  525.     //
  526.     //////////
  527.     
  528.     if (thePanoTrack != NULL)
  529.         AddTrackReference(theQTVRTrack, thePanoTrack, kQTVRPanoramaType, NULL);
  530.         
  531.     if (myImageTrack != NULL)
  532.         AddTrackReference(thePanoTrack, myImageTrack, kQTVRImageTrackRefType, &myRefIndex);
  533.  
  534.     if (myHSImageTrack != NULL)
  535.         AddTrackReference(thePanoTrack, myHSImageTrack, kQTVRHotSpotTrackRefType, &myHSRefIndex);
  536.  
  537.     //////////
  538.     //
  539.     // add a media sample to the panorama track
  540.     //
  541.     //////////
  542.     
  543.     // create a sample description; this contains no real info, but AddMediaSample requires it
  544.     mySampleDesc = (SampleDescriptionHandle)NewHandleClear(sizeof(SampleDescription));
  545.  
  546.     myErr = QTNewAtomContainer(&myPanoSample);
  547.     if (myErr != noErr)
  548.         goto bail;
  549.     
  550.     myPanoSampleData.majorVersion = EndianU16_NtoB(kQTVRMajorVersion);
  551.     myPanoSampleData.minorVersion = EndianU16_NtoB(kQTVRMinorVersion);
  552.     
  553.     // fill in the track reference indices
  554.     myPanoSampleData.imageRefTrackIndex = EndianU32_NtoB(myRefIndex);
  555.     myPanoSampleData.hotSpotRefTrackIndex = EndianU32_NtoB(myHSRefIndex);
  556.  
  557.     myPanoSampleData.minPan = 0.0;
  558.     myPanoSampleData.maxPan = 360.0;
  559.     myTheta = 180.0 * (atan((theWidth * myNumFramesX) * 3.14159 / (theHeight * myNumFramesY))) / 3.14159;
  560.     myPanoSampleData.minTilt = -myTheta;
  561.     myPanoSampleData.maxTilt = myTheta;
  562.     myPanoSampleData.minFieldOfView = 0.0;
  563.     myPanoSampleData.maxFieldOfView = myTheta * 2;
  564.     myPanoSampleData.defaultPan = 0.0;
  565.     myPanoSampleData.defaultTilt = 0.0;
  566.     myPanoSampleData.defaultFieldOfView = (myTheta * 2) * .8;
  567.  
  568.     VRPano_ConvertFloatToBigEndian(&myPanoSampleData.minPan);
  569.     VRPano_ConvertFloatToBigEndian(&myPanoSampleData.maxPan);
  570.     VRPano_ConvertFloatToBigEndian(&myPanoSampleData.minTilt);
  571.     VRPano_ConvertFloatToBigEndian(&myPanoSampleData.maxTilt);
  572.     VRPano_ConvertFloatToBigEndian(&myPanoSampleData.minFieldOfView);
  573.     VRPano_ConvertFloatToBigEndian(&myPanoSampleData.maxFieldOfView);
  574.     VRPano_ConvertFloatToBigEndian(&myPanoSampleData.defaultPan);
  575.     VRPano_ConvertFloatToBigEndian(&myPanoSampleData.defaultTilt);
  576.     VRPano_ConvertFloatToBigEndian(&myPanoSampleData.defaultFieldOfView);
  577.  
  578.     myPanoSampleData.imageSizeX = EndianU32_NtoB(theWidth * myNumFramesX);
  579.     myPanoSampleData.imageSizeY = EndianU32_NtoB(theHeight * myNumFramesY);
  580.     myPanoSampleData.imageNumFramesX = EndianU16_NtoB(myNumFramesX);
  581.     myPanoSampleData.imageNumFramesY = EndianU16_NtoB(myNumFramesY);
  582.     
  583.     myPanoSampleData.hotSpotSizeX = EndianU32_NtoB(theWidth * myNumFramesX);
  584.     myPanoSampleData.hotSpotSizeY = EndianU32_NtoB(theHeight * myNumFramesY); 
  585.     myPanoSampleData.hotSpotNumFramesX = EndianU16_NtoB(myNumFramesX);
  586.     myPanoSampleData.hotSpotNumFramesY = EndianU16_NtoB(myNumFramesY);
  587.         
  588.     myPanoSampleData.flags = EndianU32_NtoB(0L);
  589.     myPanoSampleData.reserved1 = EndianU32_NtoB(0L);
  590.     myPanoSampleData.reserved2 = EndianU32_NtoB(0L);
  591.     
  592.     // insert the pano sample atom into the pano sample atom container
  593.     myErr = QTInsertChild(myPanoSample, kParentAtomIsContainer, kQTVRPanoSampleDataAtomType, 1, 1, sizeof(QTVRPanoSampleAtom), &myPanoSampleData, NULL);
  594.     if (myErr != noErr)
  595.         goto bail;
  596.     
  597.     // add pan constraint, tilt constraint, and field-of-view constraint atoms here
  598.     // [left as an exercise for the reader; here's the basic idea:]
  599.     if (false) {
  600.         QTVRAngleRangeAtom        myPanRangeAtom;
  601.         
  602.         myPanRangeAtom.minimumAngle = 0.0;
  603.         myPanRangeAtom.maximumAngle = 270.0;
  604.         VRPano_ConvertFloatToBigEndian(&myPanRangeAtom.minimumAngle);
  605.         VRPano_ConvertFloatToBigEndian(&myPanRangeAtom.maximumAngle);
  606.         
  607.         myErr = QTInsertChild(myPanoSample, kParentAtomIsContainer, kQTVRPanConstraintAtomType, 1, 1, sizeof(QTVRAngleRangeAtom), &myPanRangeAtom, NULL);
  608.     }
  609.         
  610.     // create the media sample
  611.     BeginMediaEdits(thePanoMedia);
  612.  
  613.     myErr = AddMediaSample(thePanoMedia, (Handle)myPanoSample, 0, GetHandleSize((Handle)myPanoSample), myDuration, (SampleDescriptionHandle)mySampleDesc, 1, 0, NULL);
  614.     if (myErr != noErr)
  615.         goto bail;
  616.  
  617.     EndMediaEdits(thePanoMedia);
  618.  
  619.     // add the media to the track
  620.     myErr = InsertMediaIntoTrack(thePanoTrack, 0, 0, myDuration, fixed1);
  621.     
  622. bail:
  623.     if (mySrcMovie != NULL) 
  624.         DisposeMovie(mySrcMovie);
  625.         
  626.     if (mySrcRefNum != 0)
  627.         CloseMovieFile(mySrcRefNum);
  628.         
  629.     if (myHSSrcMovie != NULL) 
  630.         DisposeMovie(myHSSrcMovie);
  631.         
  632.     if (myHSSrcRefNum != 0)
  633.         CloseMovieFile(myHSSrcRefNum);
  634.         
  635.     return(myErr);
  636. }
  637.  
  638.  
  639. //////////
  640. //
  641. // VRPano_CreateTileMovie
  642. // Create a QuickTime movie containing tiles from the specified image file.
  643. //
  644. //////////
  645.  
  646. OSErr VRPano_CreateTileMovie (GraphicsImportComponent theImporter, FSSpec *theTileSpec, CodecType theCodec, CodecQ theQuality, short theDepth)
  647. {
  648.     Rect                        myPictRect;
  649.     Rect                        myTileRect;
  650.     long                        myTileHeight, myTileWidth;
  651.     GWorldPtr                    myGWorld = NULL;
  652.     PixMapHandle                 myPixMap = NULL;
  653.     Movie                        myMovie = NULL;
  654.     short                        myResRefNum = 0;
  655.     short                        myResID = movieInDataForkResID;
  656.     Track                        myTrack = NULL;
  657.     Media                        myMedia = NULL;
  658.     ImageDescriptionHandle        myImageDesc = NULL;
  659.     Handle                        myData = NULL;
  660.     Ptr                            myDataPtr = NULL;
  661.     long                        myFlags = createMovieFileDeleteCurFile | createMovieFileDontCreateResFile;
  662.     long                        myFrameSize;
  663.     TimeValue                    myDuration = 300;
  664.     UInt16                        myIndex;
  665.     OSErr                        myErr = noErr;
  666. #if USE_TILE_DISPLAY_WINDOW
  667.     WindowRef                    myWindow;
  668. #endif
  669.     
  670.     //////////
  671.     //
  672.     // create a GWorld to draw the uncompressed tiles into
  673.     //
  674.     //////////
  675.  
  676.     myErr = GraphicsImportGetBoundsRect(theImporter, &myPictRect);
  677.     if (myErr != noErr)
  678.         goto bail;
  679.     
  680.     myTileRect = myPictRect;
  681.     
  682.     myTileHeight = myTileRect.bottom - myTileRect.top;
  683.     myTileWidth = myTileRect.right - myTileRect.left;
  684.     myTileRect.bottom = myTileRect.top + (myTileHeight / kNumTilesInPanoFile);
  685.     myTileHeight = myTileRect.bottom - myTileRect.top;                // since we just changed myTileRect.bottom
  686.     
  687. #if USE_TILE_DISPLAY_WINDOW
  688.     MacOffsetRect(&myTileRect, 20, 100);
  689.     myWindow = NewCWindow(NULL, &myTileRect, "\pUncompressed tiles", true, noGrowDocProc, (WindowPtr)-1L, true, 0);
  690.     MacOffsetRect(&myTileRect, -20, -100);
  691. #endif
  692.     
  693.     myErr = NewGWorld(&myGWorld, theDepth, &myTileRect, NULL, NULL, 0L);
  694.     if (myErr != noErr)
  695.         goto bail;
  696.  
  697.     //////////
  698.     //
  699.     // create a new movie to contain the compressed tiles as video samples
  700.     //
  701.     //////////
  702.     
  703.     // create a movie file for the tile movie
  704.     myErr = CreateMovieFile(theTileSpec, FOUR_CHAR_CODE('TVOD'), smCurrentScript, myFlags, &myResRefNum, &myMovie);
  705.     if (myErr != noErr)
  706.         goto bail;
  707.     
  708.     //////////
  709.     //
  710.     // create the tile movie track and media
  711.     //
  712.     //////////
  713.  
  714.     myTrack = NewMovieTrack(myMovie, FixRatio(myTileWidth, 1), FixRatio(myTileHeight, 1), kNoVolume);    
  715.     myMedia = NewTrackMedia(myTrack, VideoMediaType, 600, NULL, 0);
  716.     if ((myTrack == NULL) || (myMedia == NULL))
  717.         goto bail;
  718.     
  719.     //////////
  720.     //
  721.     // dice the picture into pieces, compress them, and add the compressed tiles as video samples to the movie
  722.     //
  723.     //////////
  724.  
  725.     // create an image description handle; this is resized and filled in below by FCompressImage
  726.     myImageDesc = (ImageDescriptionHandle)NewHandleClear(4);
  727.     if (myImageDesc == NULL)
  728.         goto bail;
  729.     
  730.     BeginMediaEdits(myMedia);
  731.         
  732.     for (myIndex = 0; myIndex < kNumTilesInPanoFile; myIndex++) {
  733.     
  734.         //////////
  735.         //
  736.         // draw the picture into the tile GWorld
  737.         //
  738.         //////////
  739.  
  740.         
  741. #if USE_TILE_DISPLAY_WINDOW
  742.         // draw the picture into the window we've created
  743.         if (myWindow != NULL) {
  744.             GraphicsImportSetGWorld(theImporter, (CGrafPtr)myWindow, NULL);
  745.             GraphicsImportSetBoundsRect(theImporter, &myPictRect);
  746.             GraphicsImportDraw(theImporter);
  747.         }
  748. #endif
  749.  
  750.         // draw the picture into the uncompressed tile GWorld
  751.         GraphicsImportSetGWorld(theImporter, myGWorld, NULL);
  752.         GraphicsImportSetBoundsRect(theImporter, &myPictRect);
  753.         GraphicsImportDraw(theImporter);
  754.         
  755.         // offset the picture rectangle for next time thru the loop
  756.         MacOffsetRect(&myPictRect, 0, -myTileHeight);
  757.         
  758.         //////////
  759.         //
  760.         // compress the tile
  761.         //
  762.         //////////
  763.  
  764.         myPixMap = GetGWorldPixMap(myGWorld);
  765.         LockPixels(myPixMap);
  766.  
  767.         // find out how big a compressed frame will be
  768.         myErr = GetMaxCompressionSize(myPixMap, &myTileRect, theDepth, theQuality, theCodec, (CompressorComponent)anyCodec, &myFrameSize);
  769.         UnlockPixels(myPixMap);
  770.         if (myErr != noErr)
  771.             goto bail;
  772.  
  773.         myData = NewHandleClear(myFrameSize);
  774.         if (myData == NULL)
  775.             goto bail;
  776.  
  777.         HLock(myData);
  778.         myDataPtr = StripAddress(*myData);
  779.         
  780.         // compress the tile
  781.         LockPixels(myPixMap);
  782.         myErr = FCompressImage(myPixMap, &myTileRect, theDepth, theQuality, theCodec, (CompressorComponent)anyCodec, NULL, 0, 0, NULL, NULL, myImageDesc, myDataPtr);
  783.         UnlockPixels(myPixMap);
  784.         if (myErr != noErr)
  785.             goto bail;
  786.             
  787.         //////////
  788.         //
  789.         // add the tile to the movie
  790.         //
  791.         //////////
  792.  
  793.         myErr = AddMediaSample(myMedia, myData, 0, (**myImageDesc).dataSize, myDuration, (SampleDescriptionHandle)myImageDesc, 1L, 0, NULL);
  794.         if (myErr != noErr)
  795.             goto bail;
  796.         
  797.         DisposeHandle(myData);
  798.         myData = NULL;
  799.     }
  800.     
  801.     EndMediaEdits(myMedia);
  802.     
  803.     // add the media to the track, at time 0
  804.     myErr = InsertMediaIntoTrack(myTrack, 0, 0, GetMediaDuration(myMedia), fixed1);
  805.     if (myErr != noErr)
  806.         goto bail;
  807.  
  808.     // add the movie resource
  809.     myErr = AddMovieResource(myMovie, myResRefNum, &myResID, NULL);
  810.     
  811. bail:
  812.     if (myGWorld != NULL)
  813.         DisposeGWorld(myGWorld);
  814.  
  815.     if (myResRefNum != 0)
  816.         CloseMovieFile(myResRefNum);
  817.     
  818.     if (myData != NULL)
  819.         DisposeHandle(myData);
  820.         
  821.     if (myImageDesc != NULL)
  822.         DisposeHandle((Handle)myImageDesc);
  823.     
  824.     if (myMovie != NULL)
  825.         DisposeMovie(myMovie);
  826.     
  827. #if USE_TILE_DISPLAY_WINDOW
  828.     if (myWindow != NULL)
  829.         DisposeWindow(myWindow);
  830. #endif
  831.  
  832.     return(myErr);
  833. }
  834.  
  835.  
  836. //////////
  837. //
  838. // VRPano_CreateQTVRMovieVers1x0
  839. // Create a single-node panoramic QTVR movie from the specified tile movie(s).
  840. //
  841. // NOTE: This function builds a movie that conforms to version 1.0 of the QuickTime VR file format.
  842. //
  843. // The newly-created movie contains references to the original tile movie, not the actual movie data.
  844. // We do this because we assume that the caller will flatten the movie into a third movie, which will
  845. // contain the movie data. Also, the interim file is much smaller than it would be if we copied the data,
  846. // thus saving time and disk space.
  847. //
  848. //////////
  849.  
  850. OSErr VRPano_CreateQTVRMovieVers1x0 (FSSpec *theMovieSpec, FSSpec *theTileSpec, FSSpec *theHSTileSpec, long theHeight, long theWidth)
  851. {
  852.     PanoramaDescriptionHandle        myPanoDesc = NULL;
  853.     PanoSampleHeaderAtomHandle        myPanoHeader = NULL;
  854.     HotSpotTableAtomHandle            myHotSpotTable = NULL;
  855.     StringTableAtomHandle            myStringTable = NULL;
  856.     short                            myResRefNum = 0;
  857.     short                            myResID = movieInDataForkResID;
  858.     short                            myHSResRefNum = 0;
  859.     short                            myTilResRefNum = 0;
  860.     short                            myHSTilResRefNum = 0;
  861.     Movie                            myMovie = NULL;
  862.     Movie                            myTileMovie = NULL;
  863.     Movie                            myHSTileMovie = NULL;
  864.     Track                            myPanoTrack;
  865.     Media                            myPanoMedia;
  866.     Track                            myImageTrack;
  867.     Track                            myHSImageTrack;
  868.     long                            myFlags = createMovieFileDeleteCurFile | createMovieFileDontCreateResFile;
  869.     TimeValue                        myDuration = 7200;
  870.     short                            myNumFramesX = 1;
  871.     short                            myNumFramesY = kNumTilesInPanoFile;
  872.     float                            myTheta;
  873.     UInt16                            myIndex;
  874.     short                            myHeight = theHeight;
  875.     short                            myWidth = theWidth;
  876.     ComponentResult                    myErr = noErr;
  877.     
  878.     //////////
  879.     //
  880.     // create a new movie
  881.     //
  882.     //////////
  883.  
  884.     // create a movie file for the destination movie
  885.     myErr = CreateMovieFile(theMovieSpec, FOUR_CHAR_CODE('TVOD'), smCurrentScript, myFlags, &myResRefNum, &myMovie);
  886.     if (myErr != noErr)
  887.         goto bail;
  888.  
  889.     //////////
  890.     //
  891.     // copy the video track from the tile movie to the new movie; this is the "scene track"
  892.     //
  893.     //////////
  894.  
  895.     // open the tile movie file
  896.     myErr = OpenMovieFile(theTileSpec, &myTilResRefNum, fsRdPerm);
  897.     if (myErr != noErr)
  898.         goto bail;
  899.     
  900.     myErr = NewMovieFromFile(&myTileMovie, myTilResRefNum, NULL, 0, 0, 0);
  901.     if (myErr != noErr)
  902.         goto bail;
  903.     
  904.     SetMoviePlayHints(myTileMovie, hintsHighQuality, hintsHighQuality);
  905.  
  906.     myErr = VRPano_ImportVideoTrack(myTileMovie, myMovie, myDuration, &theWidth, &theHeight, &myImageTrack);
  907.     if (myErr != noErr)
  908.         goto bail;
  909.     
  910.     //////////
  911.     //
  912.     // copy the hot spot image track from the tile movie to the new movie
  913.     //
  914.     //////////
  915.     
  916.     // open the source file
  917.     myErr = OpenMovieFile(theHSTileSpec, &myHSTilResRefNum, fsRdPerm);
  918.     if (myErr != noErr)
  919.         goto bail;
  920.     
  921.     myErr = NewMovieFromFile(&myHSTileMovie, myHSTilResRefNum, &myHSResRefNum, 0, 0, 0);
  922.     if (myErr != noErr)
  923.         goto bail;
  924.     
  925.     myErr = VRPano_ImportVideoTrack(myHSTileMovie, myMovie, myDuration, &theWidth, &theHeight, &myHSImageTrack);
  926.     if (myErr != noErr)
  927.         goto bail;
  928.     
  929.     //////////
  930.     //
  931.     // create a panorama track and add it to the movie
  932.     //
  933.     //////////
  934.     
  935.     // create a panorama track and media
  936.     myPanoTrack = NewMovieTrack(myMovie, FixRatio(myWidth, 1), FixRatio(myHeight, 1), 0);
  937.     myPanoMedia = NewTrackMedia(myPanoTrack, kQTVROldPanoType, kQTVRStandardTimeScale, NULL, 0);
  938.     if ((myPanoTrack == NULL) || (myPanoMedia == NULL))
  939.         goto bail;
  940.  
  941.     // create a panorama sample description
  942.      myPanoDesc = (PanoramaDescriptionHandle)NewHandleClear(sizeof(PanoramaDescription));
  943.     if (myPanoDesc == NULL)
  944.         goto bail;
  945.  
  946.     // fill in the panorama sample description;
  947.     // all the data in this sample description that follows the first 4 long words must be in big-endian format;
  948.     // the first four long words are used by AddMediaSample and must therefore be in native-endian format
  949.     (**myPanoDesc).size = sizeof(PanoramaDescription);
  950.     (**myPanoDesc).type = kPanDescType;
  951.     (**myPanoDesc).reserved1 = 0L;
  952.     (**myPanoDesc).reserved2 = 0L;
  953.     
  954.     (**myPanoDesc).majorVersion = EndianU16_NtoB(0);
  955.     (**myPanoDesc).minorVersion = EndianU16_NtoB(0);
  956.     (**myPanoDesc).sceneTrackID = EndianU32_NtoB(GetTrackID(myImageTrack));
  957.     (**myPanoDesc).loResSceneTrackID = EndianU32_NtoB(0L);        // no lo-res video track
  958.     
  959.     for (myIndex = 1; myIndex < 6; myIndex++) {
  960.         (**myPanoDesc).reserved3[myIndex] = EndianU32_NtoB(0L);
  961.         (**myPanoDesc).reserved4[myIndex] = EndianU32_NtoB(0L);
  962.     }
  963.     
  964.     (**myPanoDesc).hotSpotTrackID = EndianU32_NtoB(GetTrackID(myHSImageTrack));
  965.     (**myPanoDesc).loResHotSpotTrackID = EndianU32_NtoB(0L);    // no lo-res hot spot track
  966.  
  967.     myTheta = 180.0 * (atan((theWidth * myNumFramesX) * 3.14159 / (theHeight * myNumFramesY))) / 3.14159;
  968.     
  969.     (**myPanoDesc).hPanStart = EndianS32_NtoB(0L);
  970.     (**myPanoDesc).hPanEnd = EndianS32_NtoB(360 << 16);
  971.     (**myPanoDesc).vPanTop = EndianS32_NtoB((Fixed)(myTheta * 65536));
  972.     (**myPanoDesc).vPanBottom = EndianS32_NtoB((Fixed)(-myTheta * 65536));
  973.     (**myPanoDesc).minimumZoom = EndianS32_NtoB(0L);
  974.     (**myPanoDesc).maximumZoom = EndianS32_NtoB(0L);
  975.     
  976.     (**myPanoDesc).sceneSizeX = EndianU32_NtoB((long)(theWidth * myNumFramesX));
  977.     (**myPanoDesc).sceneSizeY = EndianU32_NtoB((long)(theHeight * myNumFramesY));
  978.     (**myPanoDesc).numFrames = EndianU32_NtoB((long)myNumFramesY);
  979.     (**myPanoDesc).sceneNumFramesX = EndianU16_NtoB(myNumFramesX);
  980.     (**myPanoDesc).sceneNumFramesY = EndianU16_NtoB(myNumFramesY);
  981.     (**myPanoDesc).sceneColorDepth = EndianU16_NtoB(32);
  982.     
  983.     (**myPanoDesc).hotSpotSizeX = EndianU32_NtoB((long)(theWidth * myNumFramesX));
  984.     (**myPanoDesc).hotSpotSizeY = EndianU32_NtoB((long)(theHeight * myNumFramesY));
  985.     (**myPanoDesc).hotSpotNumFramesX = EndianU16_NtoB(myNumFramesX);
  986.     (**myPanoDesc).hotSpotNumFramesY = EndianU16_NtoB(myNumFramesY);
  987.     (**myPanoDesc).hotSpotColorDepth = EndianU16_NtoB(8);
  988.  
  989.     // create a panorama header atom;
  990.     // the data in this atom (which is *not* a QTAtom) must be in big-endian format
  991.     myPanoHeader = (PanoSampleHeaderAtomHandle)NewHandleClear(sizeof(PanoSampleHeaderAtom));
  992.     if (myPanoHeader == NULL)
  993.         goto bail;
  994.         
  995.     (**myPanoHeader).size = EndianU32_NtoB(sizeof(PanoSampleHeaderAtom));
  996.     (**myPanoHeader).type = EndianU32_NtoB(kPanHeaderType);
  997.     (**myPanoHeader).nodeID = EndianU32_NtoB(0L);
  998.  
  999.     // set the default pan, tilt, and zoom
  1000.     (**myPanoHeader).defHPan = EndianS32_NtoB(0L);
  1001.     (**myPanoHeader).defVPan = EndianS32_NtoB(0L);
  1002.     (**myPanoHeader).defZoom = EndianS32_NtoB((Fixed)(1.5 * myTheta * 65536));
  1003.  
  1004.     // set constraints for this node; use 0 for the default constraints
  1005.     (**myPanoHeader).minHPan = EndianS32_NtoB(0L);
  1006.     (**myPanoHeader).minVPan = EndianS32_NtoB(0L);
  1007.     (**myPanoHeader).maxHPan = EndianS32_NtoB(0L);
  1008.     (**myPanoHeader).maxVPan = EndianS32_NtoB(0L);
  1009.     (**myPanoHeader).maxZoom = EndianS32_NtoB(0L);
  1010.  
  1011.     (**myPanoHeader).nameStrOffset = EndianU32_NtoB(0L);        // no name or comment
  1012.     (**myPanoHeader).commentStrOffset = EndianU32_NtoB(0L);
  1013.  
  1014.     // create a string table atom;
  1015.     // the data in this atom (which is *not* a QTAtom) must be in big-endian format
  1016.     myStringTable = (StringTableAtomHandle)NewHandleClear(sizeof(StringTableAtom));
  1017.     if (myStringTable == NULL)
  1018.         goto bail;
  1019.         
  1020.     (**myStringTable).size = EndianU32_NtoB(sizeof(StringTableAtom));
  1021.     (**myStringTable).type = EndianU32_NtoB(kStringTableType);
  1022.     (**myStringTable).bunchOstrings[0] = EndianU32_NtoB(0);
  1023.  
  1024.     // create a hot spot table atom and expand the string table atom to include the
  1025.     // hot spot names and comments
  1026.     myHotSpotTable = VRPano_MakeHotSpotVers1x0(myStringTable);
  1027.  
  1028.     // append the hot spot table atom and the string table atom to the panorama header atom
  1029.     // [left as an exercise for the reader]
  1030.     
  1031.     // add the media sample to the panorama track
  1032.     BeginMediaEdits(myPanoMedia);
  1033.  
  1034.     myErr = AddMediaSample(myPanoMedia, (Handle)myPanoHeader, 0, GetHandleSize((Handle)myPanoHeader), myDuration, (SampleDescriptionHandle)myPanoDesc, 1, 0, NULL);
  1035.     if (myErr != noErr)
  1036.         goto bail;
  1037.  
  1038.     EndMediaEdits(myPanoMedia);
  1039.  
  1040.     // add the media to the track
  1041.     myErr = InsertMediaIntoTrack(myPanoTrack, 0, 0, myDuration, fixed1);
  1042.     
  1043.     //////////
  1044.     //
  1045.     // add a user data item that identifies the QTVR movie controller
  1046.     //
  1047.     //////////
  1048.     
  1049.     myErr = VRPano_SetControllerType(myMovie, kQTVROldPanoType);
  1050.     if (myErr != noErr)
  1051.         goto bail;
  1052.         
  1053.     //////////
  1054.     //
  1055.     // add the movie resource to the panorama movie
  1056.     //
  1057.     //////////
  1058.     
  1059.     myErr = AddMovieResource(myMovie, myResRefNum, &myResID, NULL);
  1060.     
  1061. bail:
  1062.     if (myPanoDesc != NULL)
  1063.         DisposeHandle((Handle)myPanoDesc);
  1064.     
  1065.     if (myPanoHeader != NULL)
  1066.         DisposeHandle((Handle)myPanoHeader);
  1067.     
  1068.     if (myResRefNum != 0)
  1069.         CloseMovieFile(myResRefNum);
  1070.     
  1071.     if (myHSResRefNum != 0)
  1072.         CloseMovieFile(myHSResRefNum);
  1073.     
  1074.     if (myMovie != NULL)
  1075.         DisposeMovie(myMovie);
  1076.         
  1077.     if (myTilResRefNum != 0)
  1078.         CloseMovieFile(myTilResRefNum);
  1079.     
  1080.     if (myHSTilResRefNum != 0)
  1081.         CloseMovieFile(myHSTilResRefNum);
  1082.     
  1083.     if (myTileMovie != NULL)
  1084.         DisposeMovie(myTileMovie);
  1085.         
  1086.     if (myHSTileMovie != NULL)
  1087.         DisposeMovie(myHSTileMovie);
  1088.         
  1089.     return(myErr);
  1090. }
  1091.  
  1092.  
  1093. //////////
  1094. //
  1095. // VRPano_CreateQTVRMovieVers2x0
  1096. // Create a single-node panoramic QTVR movie from the specified tile movie(s).
  1097. //
  1098. // NOTE: This function builds a movie that conforms to version 2.0 of the QuickTime VR file format.
  1099. //
  1100. // The newly-created movie contains references to the original tile movie, not the actual movie data.
  1101. // We do this because we assume that the caller will flatten the movie into a third movie, which will
  1102. // contain the movie data. Also, the interim file is much smaller than it would be if we copied the data,
  1103. // thus saving time and disk space.
  1104. //
  1105. //////////
  1106.  
  1107. OSErr VRPano_CreateQTVRMovieVers2x0 (FSSpec *theMovieSpec, FSSpec *theTileSpec, FSSpec *theHSTileSpec, long theHeight, long theWidth)
  1108. {
  1109.     Handle                            myHandle = NULL;
  1110.     SampleDescriptionHandle            mySampleDesc = NULL;
  1111.     QTVRSampleDescriptionHandle        myQTVRDesc = NULL;
  1112.     short                            myResRefNum = 0;
  1113.     Movie                            myMovie = NULL;
  1114.     Track                            myQTVRTrack;
  1115.     Media                            myQTVRMedia;
  1116.     Track                            myPanoTrack;
  1117.     Media                            myPanoMedia;
  1118.     long                            mySize;
  1119.     long                            myFlags = createMovieFileDeleteCurFile | createMovieFileDontCreateResFile;
  1120.     TimeValue                        myDuration = 7200;
  1121.     QTAtomContainer                    myVRWorld;
  1122.     QTAtomContainer                    myNodeInfo;
  1123.     ComponentResult                    myErr = noErr;
  1124.     
  1125.     //////////
  1126.     //
  1127.     // create a new movie
  1128.     //
  1129.     //////////
  1130.  
  1131.     // create a movie file for the destination movie
  1132.     myErr = CreateMovieFile(theMovieSpec, FOUR_CHAR_CODE('TVOD'), smCurrentScript, myFlags, &myResRefNum, &myMovie);
  1133.     if (myErr != noErr)
  1134.         goto bail;
  1135.  
  1136.     //////////
  1137.     //
  1138.     // create the QTVR movie track and media
  1139.     //
  1140.     //////////
  1141.  
  1142.     myQTVRTrack = NewMovieTrack(myMovie, FixRatio(theWidth, 1), FixRatio(theHeight, 1), kFullVolume);
  1143.     myQTVRMedia = NewTrackMedia(myQTVRTrack, kQTVRQTVRType, kQTVRStandardTimeScale, NULL, 0);
  1144.     if ((myQTVRTrack == NULL) || (myQTVRMedia == NULL))
  1145.         goto bail;
  1146.         
  1147.     SetMovieTimeScale(myMovie, kQTVRStandardTimeScale);
  1148.  
  1149.     // create a VR world atom container and a node information atom container;
  1150.     // remember that the VR world becomes part of the QTVR sample description,
  1151.     // and the node information atom container becomes the media sample data
  1152.     myErr = VRPano_CreateVRWorld(&myVRWorld, &myNodeInfo, kQTVRPanoramaType);
  1153.     if (myErr != noErr)
  1154.         goto bail;
  1155.         
  1156.     if ((myVRWorld == NULL) || (myNodeInfo == NULL))
  1157.         goto bail;
  1158.     
  1159.     // create a QTVR sample description
  1160.     mySize = sizeof(QTVRSampleDescription) + GetHandleSize((Handle)myVRWorld) - sizeof(long);
  1161.     myQTVRDesc = (QTVRSampleDescriptionHandle)NewHandleClear(mySize);
  1162.     if (myQTVRDesc == NULL)
  1163.         goto bail;
  1164.         
  1165.     (**myQTVRDesc).descSize = mySize;
  1166.     (**myQTVRDesc).descType = kQTVRQTVRType;
  1167.     (**myQTVRDesc).reserved1 = 0;
  1168.     (**myQTVRDesc).reserved2 = 0;
  1169.     (**myQTVRDesc).dataRefIndex = 0;
  1170.  
  1171.     // copy the VR world atom container into the data field of the QTVR sample description
  1172.     BlockMove(*((Handle)myVRWorld), &((**myQTVRDesc).data), GetHandleSize((Handle)myVRWorld));
  1173.     
  1174.     // create the media sample
  1175.     BeginMediaEdits(myQTVRMedia);
  1176.  
  1177.     myErr = AddMediaSample(myQTVRMedia, (Handle)myNodeInfo, 0, GetHandleSize((Handle)myNodeInfo), myDuration, (SampleDescriptionHandle)myQTVRDesc, 1, 0, NULL);
  1178.     if (myErr != noErr)
  1179.         goto bail;
  1180.  
  1181.     EndMediaEdits(myQTVRMedia);
  1182.     
  1183.     // add the media to the track
  1184.     InsertMediaIntoTrack(myQTVRTrack, 0, 0, myDuration, fixed1);
  1185.     
  1186.     //////////
  1187.     //
  1188.     // create a panorama track and add it to the movie
  1189.     //
  1190.     //////////
  1191.     
  1192.     // create panorama track and media
  1193.     myPanoTrack = NewMovieTrack(myMovie, FixRatio(theWidth, 1), FixRatio(theHeight, 1), kNoVolume);
  1194.     myPanoMedia = NewTrackMedia(myPanoTrack, kQTVRPanoramaType, kQTVRStandardTimeScale, NULL, 0);
  1195.     if ((myPanoTrack == NULL) || (myPanoMedia == NULL))
  1196.         goto bail;
  1197.     
  1198.     // create the panorama image track and configure the panorama track
  1199.     myErr = VRPano_CreatePanoTrack(myMovie, theTileSpec, theHSTileSpec, myQTVRTrack, myPanoTrack, myPanoMedia, theWidth, theHeight);
  1200.     if (myErr != noErr)
  1201.         goto bail;
  1202.         
  1203.     //////////
  1204.     //
  1205.     // add a user data item that identifies the QTVR movie controller
  1206.     //
  1207.     //////////
  1208.     
  1209.     myErr = VRPano_SetControllerType(myMovie, kQTVRQTVRType);
  1210.     if (myErr != noErr)
  1211.         goto bail;
  1212.         
  1213.     //////////
  1214.     //
  1215.     // add the movie resource to the panorama movie
  1216.     //
  1217.     //////////
  1218.     
  1219.     myErr = AddMovieResource(myMovie, myResRefNum, NULL, NULL);
  1220.     
  1221. bail:
  1222.     if (mySampleDesc != NULL)
  1223.         DisposeHandle((Handle)mySampleDesc);
  1224.     
  1225.     if (myQTVRDesc != NULL)
  1226.         DisposeHandle((Handle)myQTVRDesc);
  1227.     
  1228.     if (myResRefNum != 0)
  1229.         CloseMovieFile(myResRefNum);
  1230.     
  1231.     if (myVRWorld != NULL)
  1232.         QTDisposeAtomContainer(myVRWorld);
  1233.         
  1234.     if (myNodeInfo != NULL)
  1235.         QTDisposeAtomContainer(myNodeInfo);
  1236.  
  1237.     if (myMovie != NULL)
  1238.         DisposeMovie(myMovie);
  1239.         
  1240.     return(myErr);
  1241. }
  1242.  
  1243.  
  1244. //////////
  1245. //
  1246. // VRPano_MakePanorama
  1247. // Create a single-node QuickTime VR panoramic movie from the specified image file.
  1248. //
  1249. //////////
  1250.  
  1251. OSErr VRPano_MakePanorama (FSSpec *thePictSpec, FSSpec *theHSPictSpec, FSSpec *theTileSpec, FSSpec *theDestSpec, long theWidth, long theHeight, CodecType theCodec, CodecQ theQuality, long theVersion)
  1252. {
  1253.     long                        myPictHeight, myPictWidth;
  1254.     GraphicsImportComponent        myImporter = NULL;
  1255.     Rect                        myRect;
  1256.     FSSpec                        myTileSpec = *theTileSpec;
  1257.     FSSpec                        myDestSpec = *theDestSpec;
  1258.     FSSpec                        myTempSpec;
  1259.     FSSpec                        myHSTileSpec;
  1260.     Movie                        myTempMovie = NULL;
  1261.     Movie                        myPanoMovie = NULL;
  1262.     short                        myTempResRefNum = 0;
  1263.     OSErr                        myErr = noErr;
  1264.  
  1265.     //////////
  1266.     //
  1267.     // get a graphics importer for the image file and determine the natural size of the image
  1268.     //
  1269.     //////////
  1270.  
  1271.     myErr = GetGraphicsImporterForFile(thePictSpec, &myImporter);
  1272.     if (myErr != noErr)
  1273.         goto bail;
  1274.     
  1275.     myErr = GraphicsImportGetNaturalBounds(myImporter, &myRect);
  1276.     if (myErr != noErr)
  1277.         goto bail;
  1278.     
  1279.     myPictHeight = myRect.bottom - myRect.top,
  1280.     myPictWidth = myRect.right - myRect.left;
  1281.  
  1282.     //////////
  1283.     //
  1284.     // verify the orientation and dimensions of the picture
  1285.     //
  1286.     //////////
  1287.  
  1288.     // check for correct orientation: the image must be taller than wide;
  1289.     // if not, we cannot continue
  1290.     if (myPictHeight < myPictWidth)
  1291.         goto bail;
  1292.         
  1293.     // check for correct dimensions: height must be divisible by 96 and width by 4;
  1294.     // if not, we'll adjust the dimensions
  1295.     if (((myPictHeight % 96) != 0) || ((myPictWidth % 4) != 0)) {
  1296.     
  1297.         // make sure height is divisible by 96
  1298.         if ((myPictHeight % 96) != 0) {
  1299.             UInt16            myNewHeight;
  1300.             UInt16            myNewWidth;
  1301.             double            myScale;
  1302.             
  1303.             myNewHeight = myPictHeight - (myPictHeight % 96);
  1304.             myScale = (double)myNewHeight / (double)myPictHeight;
  1305.             myNewWidth = (UInt16)((double)myPictWidth * myScale);
  1306.             myNewWidth = myNewWidth - (myNewWidth % 4);
  1307.         }
  1308.         
  1309.         // make sure width is divisible by 4
  1310.         if ((myPictWidth % 4) != 0) {
  1311.             UInt16            myNewWidth;
  1312.  
  1313.             myNewWidth = myPictWidth - (myPictWidth % 4);
  1314.         }
  1315.     }
  1316.     
  1317.     //////////
  1318.     //
  1319.     // dice the panoramic image into compressed tiles and put the tiles into a movie
  1320.     //
  1321.     //////////
  1322.  
  1323.     myErr = VRPano_CreateTileMovie(myImporter, theTileSpec, theCodec, theQuality, 0);
  1324.     if (myErr != noErr)
  1325.         goto bail;
  1326.  
  1327.     CloseComponent(myImporter);
  1328.     
  1329.     //////////
  1330.     //
  1331.     // dice the hot spot image into compressed tiles and put the tiles into a movie,
  1332.     // if the user has selected a hot spot image file
  1333.     //
  1334.     //////////
  1335.     
  1336.     if (theHSPictSpec->name[0] != 0) {
  1337.     
  1338.         // get a graphics importer for the hot spot image file
  1339.         myErr = GetGraphicsImporterForFile(theHSPictSpec, &myImporter);
  1340.         if (myErr != noErr)
  1341.             goto bail;
  1342.     
  1343.         // create a file specification for the hot spot tile file
  1344.         myHSTileSpec = *theDestSpec;
  1345.         
  1346.         // to create a new file name, we'll just change the last character of the destination movie file name
  1347.         // (no doubt you could do a better job here!)
  1348.         if (myHSTileSpec.name[myHSTileSpec.name[0]] == 's')
  1349.             myHSTileSpec.name[myHSTileSpec.name[0]] = '#';
  1350.         else
  1351.             myHSTileSpec.name[myHSTileSpec.name[0]] = 's';
  1352.  
  1353.         // a hot spot image track must be compressed with a lossless compressor and must be 8 bits deep
  1354.         myErr = VRPano_CreateTileMovie(myImporter, &myHSTileSpec, kGraphicsCodecType, codecLosslessQuality, 8);
  1355.         if (myErr != noErr)
  1356.             goto bail;
  1357.     } else {
  1358.     
  1359.         // if no hot spot image, clear out myHSTileSpec
  1360.         myHSTileSpec.vRefNum = 0;
  1361.         myHSTileSpec.parID = 0;
  1362.         myHSTileSpec.name[0] = 0;        // set length to 0
  1363.     }
  1364.  
  1365.     //////////
  1366.     //
  1367.     // create a temporary version of the panorama movie file,
  1368.     // located in the same directory as the destination panorama movie file
  1369.     //
  1370.     //////////
  1371.     
  1372.     // to create a new file name, we'll just change the last character of the destination movie file name
  1373.     // (no doubt you could do a better job here!)
  1374.     myTempSpec = *theDestSpec;
  1375.     
  1376.     if (myTempSpec.name[myTempSpec.name[0]] == 't')
  1377.         myTempSpec.name[myTempSpec.name[0]] = '@';
  1378.     else
  1379.         myTempSpec.name[myTempSpec.name[0]] = 't';
  1380.     
  1381.     // create a single node panoramic movie in the temp file
  1382.     if (theVersion == kQTVRVersion1)
  1383.         myErr = VRPano_CreateQTVRMovieVers1x0(&myTempSpec, theTileSpec, &myHSTileSpec, theHeight, theWidth);
  1384.     else if (theVersion == kQTVRVersion2)
  1385.         myErr = VRPano_CreateQTVRMovieVers2x0(&myTempSpec, theTileSpec, &myHSTileSpec, theHeight, theWidth);
  1386.         
  1387.     if (myErr != noErr)
  1388.         goto bail;
  1389.  
  1390.     //////////
  1391.     //
  1392.     // create the final, flattened movie
  1393.     //
  1394.     //////////
  1395.  
  1396.     myErr = OpenMovieFile(&myTempSpec, &myTempResRefNum, fsRdPerm);
  1397.     if (myErr != noErr)
  1398.         goto bail;
  1399.                 
  1400.     myErr = NewMovieFromFile(&myTempMovie, myTempResRefNum, NULL, 0, 0, 0);
  1401.     if (myErr != noErr)
  1402.         goto bail;
  1403.         
  1404.     SetMovieProgressProc(myTempMovie, (MovieProgressUPP)-1, 0L);
  1405.  
  1406.     // flatten the temporary file into a new movie file;
  1407.     // put the movie resource first so that FastStart is possible
  1408.     myPanoMovie = FlattenMovieData(myTempMovie, flattenDontInterleaveFlatten | flattenAddMovieToDataFork | flattenForceMovieResourceBeforeMovieData, &myDestSpec, FOUR_CHAR_CODE('TVOD'), smSystemScript, createMovieFileDeleteCurFile | createMovieFileDontCreateResFile);
  1409.  
  1410. bail:
  1411.     if (myImporter != NULL)
  1412.         CloseComponent(myImporter);
  1413.     
  1414.     if (myPanoMovie != NULL)
  1415.         DisposeMovie(myPanoMovie);
  1416.  
  1417.     if (myTempMovie != NULL)
  1418.         DisposeMovie(myTempMovie);
  1419.         
  1420.     if (myTempResRefNum != 0)
  1421.         CloseMovieFile(myTempResRefNum);
  1422.         
  1423.     DeleteMovieFile(&myTempSpec);
  1424.     DeleteMovieFile(&myHSTileSpec);
  1425.                     
  1426.     return(myErr);
  1427. }
  1428.  
  1429.  
  1430. //////////
  1431. //
  1432. // VRPano_ImportVideoTrack
  1433. // Copy a video track from one movie (the source) to another (the destination).
  1434. //
  1435. //////////
  1436.  
  1437. OSErr VRPano_ImportVideoTrack (Movie theSrcMovie, Movie theDstMovie, TimeValue theDuration, long *theTrackWidth, long *theTrackHeight, Track *theTrack)
  1438. {
  1439.     Track            mySrcTrack;
  1440.     Media            mySrcMedia;
  1441.     Track            myDstTrack;
  1442.     Media            myDstMedia;
  1443.     Fixed            myWidth, myHeight;
  1444.     OSType            myType;
  1445.     
  1446.     ClearMoviesStickyError();
  1447.     
  1448.     // get the first video track in the source movie
  1449.     mySrcTrack = GetMovieIndTrackType(theSrcMovie, 1, VideoMediaType, movieTrackMediaType);
  1450.     if (mySrcTrack == NULL)
  1451.         return(paramErr);
  1452.     
  1453.     // get the track's media and dimensions
  1454.     mySrcMedia = GetTrackMedia(mySrcTrack);
  1455.     GetTrackDimensions(mySrcTrack, &myWidth, &myHeight);
  1456.     
  1457.     // create a destination track
  1458.     myDstTrack = NewMovieTrack(theDstMovie, myWidth, myHeight, GetTrackVolume(mySrcTrack));
  1459.     if (theTrackWidth != NULL)
  1460.         *theTrackWidth = myWidth >> 16;
  1461.     if (theTrackHeight != NULL)
  1462.         *theTrackHeight = myHeight >> 16;
  1463.     if (theTrack != NULL)
  1464.         *theTrack = myDstTrack;
  1465.  
  1466.     // create a destination media
  1467.     GetMediaHandlerDescription(mySrcMedia, &myType, 0, 0);
  1468.     myDstMedia = NewTrackMedia(myDstTrack, myType, GetMediaTimeScale(mySrcMedia), 0, 0);
  1469.         
  1470.     // copy the entire track
  1471.     InsertTrackSegment(mySrcTrack, myDstTrack, 0, GetTrackDuration(mySrcTrack), 0);
  1472.     CopyTrackSettings(mySrcTrack, myDstTrack);
  1473.     SetTrackLayer(myDstTrack, GetTrackLayer(mySrcTrack));
  1474.  
  1475.     // a panorama image track should always be disabled
  1476.     SetTrackEnabled(myDstTrack, false);
  1477.  
  1478.     // make sure that the track segment equals the length of the panorama sample
  1479.     ScaleTrackSegment(myDstTrack, 0, GetTrackDuration(myDstTrack), theDuration);
  1480.  
  1481.     return(GetMoviesStickyError());
  1482. }
  1483.  
  1484.  
  1485. //////////
  1486. //
  1487. // VRPano_MakeHotSpotVers1x0
  1488. // Create a hot spot table atom with atoms for a single hot spot; also, if necessary,
  1489. // resize the string table handle theStringTable to contain a name and comment for the
  1490. // hot spot.
  1491. //
  1492. // NOTE: This function builds hot spots that conform to version 1.0 of the QuickTime VR file format.
  1493. //
  1494. //////////
  1495.  
  1496. HotSpotTableAtomHandle VRPano_MakeHotSpotVers1x0 (StringTableAtomHandle theStringTable)
  1497. {
  1498.     HotSpotTableAtomHandle    myHandle = NULL;
  1499.     
  1500.     // [left as an exercise for the reader]
  1501.     
  1502.     return(myHandle);
  1503. }
  1504.  
  1505.  
  1506. //////////
  1507. //
  1508. // VRPano_MakeHotSpotVers2x0
  1509. // Create the necessary atoms inside of theNodeInfo to configure the specified hot spot
  1510. // as a URL hot spot linked to the specified URL.
  1511. //
  1512. // NOTE: This function builds hot spots that conform to version 2.0 of the QuickTime VR file format.
  1513. //
  1514. //////////
  1515.  
  1516. OSErr VRPano_MakeHotSpotVers2x0 (QTAtomContainer theNodeInfo, QTAtom theHSParent, char *theURL, char *theHSName, UInt32 theIndex)
  1517. {
  1518.     QTAtom                    myHSAtom;
  1519.     QTVRHotSpotInfoAtom     myHSInfoAtom;
  1520.     Str255                    myHSName;
  1521.     QTAtomID                myHSNameID;
  1522.     OSErr                    myErr = noErr;
  1523.  
  1524.     //////////
  1525.     //
  1526.     // add a hot spot atom to the specified node info atom
  1527.     //
  1528.     // a hot spot atom contains two children:
  1529.     // a hot spot information atom, which contains general info about the hot spot,
  1530.     // and (for URL hot spots) a URL hot spot atom
  1531.     //
  1532.     //////////
  1533.     
  1534.     // the atom ID should be the same as the hot spot ID, which is an index in a 8-bit color table    
  1535.     myErr = QTInsertChild(theNodeInfo, theHSParent, kQTVRHotSpotAtomType, theIndex, 0, 0, NULL, &myHSAtom);
  1536.     if (myErr != noErr)
  1537.         goto bail;
  1538.     
  1539.     //////////
  1540.     //
  1541.     // add a hot spot information atom
  1542.     //
  1543.     //////////
  1544.     
  1545.     // fill in the fields of the hot spot information atom data structure
  1546.     myHSInfoAtom.majorVersion = EndianU16_NtoB(kQTVRMajorVersion);
  1547.     myHSInfoAtom.minorVersion = EndianU16_NtoB(kQTVRMinorVersion);
  1548.     myHSInfoAtom.hotSpotType = EndianU32_NtoB(kQTVRHotSpotURLType);
  1549.  
  1550.     // the published documentation says that the hot spot name is contained in a string atom
  1551.     // that is a sibling of the hot spot atom (that is, a child of the hot spot parent atom);
  1552.     // some other documents indicate that a string atom is always a sibling of the atom that
  1553.     // contains the reference (in this case, a sibling of the hot spot information atom, and
  1554.     // hence a child of the hot spot atom); I'd recommend coding to the latter....
  1555.     
  1556.     // add the hot spot name atom
  1557.     myHSName[0] = strlen(theHSName);
  1558.     strcpy((char *)&myHSName[1], theHSName);
  1559.     myErr = VRPano_AddStr255ToAtomContainer(theNodeInfo, myHSAtom, myHSName, &myHSNameID);
  1560.     if (myErr != noErr)
  1561.         goto bail;
  1562.     
  1563.     myHSInfoAtom.nameAtomID = EndianU32_NtoB(myHSNameID);
  1564.     
  1565.     // add the hot spot comment atom; 0 means that no comment atom exists
  1566.     myHSInfoAtom.commentAtomID = EndianU32_NtoB(0L);
  1567.     
  1568.     // set the custom cursor IDs; 0 means that no custom cursors exist
  1569.     myHSInfoAtom.cursorID[0] = 0;
  1570.     myHSInfoAtom.cursorID[1] = 0;
  1571.     myHSInfoAtom.cursorID[2] = 0;
  1572.     
  1573.     // set viewing hints
  1574.     myHSInfoAtom.bestPan = 0.0;
  1575.     myHSInfoAtom.bestTilt = 0.0;
  1576.     myHSInfoAtom.bestFOV = 0.0;
  1577.  
  1578.     VRPano_ConvertFloatToBigEndian(&myHSInfoAtom.bestPan);
  1579.     VRPano_ConvertFloatToBigEndian(&myHSInfoAtom.bestTilt);
  1580.     VRPano_ConvertFloatToBigEndian(&myHSInfoAtom.bestFOV);
  1581.     
  1582.     myHSInfoAtom.bestViewCenter.x = 0.0;
  1583.     myHSInfoAtom.bestViewCenter.y = 0.0;
  1584.  
  1585.     // set hot spot bounding rectangle; apparently unused
  1586.     myHSInfoAtom.hotSpotRect.top = 0;
  1587.     myHSInfoAtom.hotSpotRect.left = 0;
  1588.     myHSInfoAtom.hotSpotRect.bottom = 0;
  1589.     myHSInfoAtom.hotSpotRect.right = 0;
  1590.  
  1591.     myHSInfoAtom.flags = 0L;
  1592.     myHSInfoAtom.reserved1 = 0L;
  1593.     myHSInfoAtom.reserved2 = 0L;
  1594.     
  1595.     // insert the hot spot information atom into the hot spot atom
  1596.     myErr = QTInsertChild(theNodeInfo, myHSAtom, kQTVRHotSpotInfoAtomType, 1, 0, sizeof(myHSInfoAtom), &myHSInfoAtom, NULL);
  1597.     if (myErr != noErr)
  1598.         goto bail;
  1599.         
  1600.     //////////
  1601.     //
  1602.     // add a URL hot spot atom as a child of the hot spot atom
  1603.     //
  1604.     //////////
  1605.     
  1606.     // the atom data is the URL text
  1607.     // (not a Pascal or C string, but just the characters themselves)
  1608.     myErr = QTInsertChild(theNodeInfo, myHSAtom, kQTVRHotSpotURLType, 1, 0, strlen(theURL), theURL, NULL);
  1609.  
  1610. bail:
  1611.     return(myErr);
  1612. }
  1613.  
  1614.  
  1615. //////////
  1616. //
  1617. // VRPano_SetControllerType
  1618. // Set the controller type of the specified movie.
  1619. //
  1620. // This function adds an item to the movie's user data;
  1621. // the updated user data is written to the movie file when the movie is next updated
  1622. // (by calling AddMovieResource or UpdateMovieResource).
  1623. //
  1624. //////////
  1625.  
  1626. OSErr VRPano_SetControllerType (Movie theMovie, OSType theType)
  1627. {
  1628.     UserData        myUserData;
  1629.     OSErr            myErr = noErr;
  1630.  
  1631.     // make sure we've got a movie
  1632.     if (theMovie == NULL)
  1633.         return(paramErr);
  1634.         
  1635.     // get the movie's user data list
  1636.     myUserData = GetMovieUserData(theMovie);
  1637.     if (myUserData == NULL)
  1638.         return(paramErr);
  1639.     
  1640.     theType = EndianU32_NtoB(theType);
  1641.     myErr = SetUserDataItem(myUserData, &theType, sizeof(theType), kQTControllerType, 0);
  1642.  
  1643.     return(myErr);
  1644. }
  1645.  
  1646.  
  1647. //////////
  1648. //
  1649. // VRPano_AddStr255ToAtomContainer
  1650. // Add a Pascal string to the specified atom container; return (through theID) the ID of the new string atom.
  1651. //
  1652. //////////
  1653.  
  1654. OSErr VRPano_AddStr255ToAtomContainer (QTAtomContainer theContainer, QTAtom theParent, Str255 theString, QTAtomID *theID)
  1655. {
  1656.     OSErr                    myErr = noErr;
  1657.  
  1658.     *theID = 0;                // initialize the returned atom ID
  1659.     
  1660.     if ((theContainer == NULL) || (theParent == 0))
  1661.         return(paramErr);
  1662.         
  1663.     if (theString[0] != 0) {
  1664.         QTAtom                myStringAtom;
  1665.         UInt16                mySize;
  1666.         QTVRStringAtomPtr    myStringAtomPtr = NULL;
  1667.         
  1668.         mySize = sizeof(QTVRStringAtom) - 4 + theString[0] + 1;
  1669.         myStringAtomPtr = (QTVRStringAtomPtr)NewPtrClear(mySize);
  1670.         
  1671.         if (myStringAtomPtr != NULL) {
  1672.             myStringAtomPtr->stringUsage = EndianU16_NtoB(1);
  1673.             myStringAtomPtr->stringLength = EndianU16_NtoB(theString[0]);
  1674.             BlockMove(theString + 1, myStringAtomPtr->theString, theString[0]);
  1675.             myStringAtomPtr->theString[theString[0]] = '\0';
  1676.             myErr = QTInsertChild(theContainer, theParent, kQTVRStringAtomType, 0, 0, mySize, (Ptr)myStringAtomPtr, &myStringAtom);
  1677.             DisposePtr((Ptr)myStringAtomPtr);
  1678.             
  1679.             if (myErr == noErr)
  1680.                 QTGetAtomTypeAndID(theContainer, myStringAtom, NULL, theID);
  1681.         }
  1682.     }
  1683.     
  1684.     return(myErr);
  1685. }
  1686.  
  1687.  
  1688. //////////
  1689. //
  1690. // VRPano_ConvertFloatToBigEndian
  1691. // Convert the specified floating-point number to big-endian format.
  1692. //
  1693. //////////
  1694.  
  1695. void VRPano_ConvertFloatToBigEndian (float *theFloat)
  1696. {
  1697.     unsigned long        *myLongPtr;
  1698.     
  1699.     myLongPtr = (unsigned long *)theFloat;
  1700.     *myLongPtr = EndianU32_NtoB(*myLongPtr);
  1701. }
  1702.  
  1703.  
  1704.